﻿using System.Linq;
using CID.Domain.Development;
using CID.Domain.Exceptions;
using CID.Tests.Support;
using NUnit.Framework;
using Rhino.Mocks;
using CID.Domain.Infrastructure;

namespace CID.Domain.Tests.Development
{
    [TestFixture]
    class VersionTests : BaseFunctionalTests
    {
        public const string Name = "Version Name";
        public const string Description = "Version Description";

        private Version version;

        private static Version CreateVersion(IComponent component = null, string name = Name, string description = Description)
        {
            return new Version(component, name, description);
        }

        public override void SetUp()
        {
            base.SetUp();
            version = CreateVersion();
        }

        public static void AssertVersion(IVersion version, IComponent component)
        {
            Assert.AreEqual(Name, version.Name);
            Assert.AreEqual(Description, version.Description);
            Assert.AreEqual(component, version.Component);
            Assert.AreEqual(version, version.DeploymentDefinition.Version);
            Assert.IsInstanceOf<Version>(version);
        }

        #region Constructor
        [Test]
        public void CreateVersion_ShouldSucceed()
        {
            var component = StubsBuilder.BuildComponentStub();
            
            version = new Version(component, Name, Description);

            AssertVersion(version, component);
        }
        #endregion

        #region GetFullName
        [Test]
        public void Test_GetFullName_ShouldSucceed()
        {
            version = CreateVersion(StubsBuilder.BuildComponentStub());

            var fullName = version.GetFullName();

            StringAssert.StartsWith(version.Component.Name, fullName);
            StringAssert.EndsWith(version.Name, fullName);
        }
        #endregion

        #region SetRelease
        [Test]
        public void SetRelease_VersionWithoutReleaseAssigned_ShouldSucceed()
        {
            var release = StubsBuilder.BuildReleaseStub();
                
            version.SetRelease(release);
            
            Assert.AreEqual(release, version.Release);
        }

        [Test, ExpectedException(typeof(ParentAlreadyAssignedException))]
        public void SetRelease_VersionWithAnotherReleaseYetAssigned_ShouldThrowException()
        {
            var oneRelease = StubsBuilder.BuildReleaseStub();
            var anotherRelease = StubsBuilder.BuildReleaseStub();

            version.SetRelease(oneRelease);

            version.SetRelease(anotherRelease);
        }
        #endregion

        #region ResetRelease
        [Test]
        public void ResetRelease_VersionWithNonReleasedReleaseAssigned_ShouldSucceed()
        {
            var release = StubsBuilder.BuildReleaseStub(ReleaseStatus.Planned);
            version.SetRelease(release);

            version.ResetRelease();

            Assert.IsNull(version.Release);
        }

        [Test, ExpectedException(typeof(InvalidActionForReleaseStatusException))]
        public void ResetRelease_VersionWithReleasedReleaseAssigned_ShouldThrowException()
        {
            var release = StubsBuilder.BuildReleaseStub(ReleaseStatus.Released);
            
            version.SetRelease(release);

            version.ResetRelease();
        }
        #endregion

        #region AddDependency
        [Test]
        public void AddDependencyOnce_VersionWithNoDepedenciesAdded_OneDependencyShouldBeAdded()
        {
            var ownerComponent = StubsBuilder.BuildComponentStub();
            var dependencyComponent = StubsBuilder.BuildComponentStub();
            version = CreateVersion(ownerComponent);

            version.AddDependency(dependencyComponent);

            Assert.AreEqual(1, version.DependsOn.Count());
            dependencyComponent.AssertWasCalled(c => c.AddDependant(version));
        }

        [Test]
        public void AddDependencyTwice_VersionWithNoDepedenciesAdded_OneDependencyShouldBeAdded()
        {
            var ownerComponent = StubsBuilder.BuildComponentStub();
            var dependencyComponent = StubsBuilder.BuildComponentStub();
            version = CreateVersion(ownerComponent);

            version.AddDependency(dependencyComponent);
            version.AddDependency(dependencyComponent);

            Assert.AreEqual(1, version.DependsOn.Count());
        }

        [Test, ExpectedException(typeof(InvalidActionException))]
        public void AddDependency_DepedencyEqualsToOwner_ShouldThrowException()
        {
            var ownerComponent = StubsBuilder.BuildComponentStub();
            version = CreateVersion(ownerComponent);

            version.AddDependency(ownerComponent);
        }

        [Test, ExpectedException(typeof(InvalidActionException))]
        public void AddDependency_DepedencyDependsDirectlyOnOwner_ShouldThrowException()
        {
            var ownerComponent = StubsBuilder.BuildComponentStub(false);
            version = CreateVersion(ownerComponent);
            ownerComponent.Stub(x => x.Versions).Return(new IVersion[] { version });

            var anotherComponent = StubsBuilder.BuildComponentStub(false);
            var anotherVersion = CreateVersion(anotherComponent);
            anotherComponent.Stub(x => x.Versions).Return(new IVersion[] { anotherVersion });
            anotherVersion.AddDependency(ownerComponent);

            version.AddDependency(anotherComponent);
        }

        [Test, ExpectedException(typeof(InvalidActionException))]
        public void AddDependency_DepedencyDependsIndirectlyOnOwner_ShouldThrowException()
        {
            var ownerComponent = StubsBuilder.BuildComponentStub(false);
            version = CreateVersion(ownerComponent);
            ownerComponent.Stub(x => x.Versions).Return(new IVersion[] { version });

            var anotherComponent1 = StubsBuilder.BuildComponentStub(false);
            var anotherVersion1 = CreateVersion(anotherComponent1, "AnotherVersion1");
            anotherComponent1.Stub(x => x.Versions).Return(new IVersion[] { anotherVersion1 });
            anotherVersion1.AddDependency(ownerComponent);

            var anotherComponent2 = StubsBuilder.BuildComponentStub(false);
            var anotherVersion2 = CreateVersion(anotherComponent2, "AnotherVersion2");
            anotherComponent2.Stub(x => x.Versions).Return(new IVersion[] { anotherVersion2 });
            anotherVersion2.AddDependency(anotherComponent1);

            version.AddDependency(anotherComponent2);
        }
        #endregion

        #region RemoveDependency
        [Test]
        public void RemoveDependency_VersionContainsThatDependency_OneDependencyShouldBeRemoved()
        {
            var ownerComponent = StubsBuilder.BuildComponentStub();
            var dependencyOne = StubsBuilder.BuildComponentStub();
            var dependencyTwo = StubsBuilder.BuildComponentStub();
            version = CreateVersion(ownerComponent);
            version.AddDependency(dependencyOne);
            version.AddDependency(dependencyTwo);

            version.RemoveDepedency(dependencyOne);

            Assert.AreEqual(1, version.DependsOn.Count());
            CollectionAssert.Contains(version.DependsOn, dependencyTwo);
            dependencyOne.AssertWasCalled(x => x.RemoveDependant(version));
        }

        [Test]
        public void RemoveDependency_VersionDoesNotContainThatDependency_NoDependencyShouldBeRemoved()
        {
            var ownerComponent = StubsBuilder.BuildComponentStub();
            var dependencyOne = StubsBuilder.BuildComponentStub();
            var dependencyTwo = StubsBuilder.BuildComponentStub();
            version = CreateVersion(ownerComponent);
            version.AddDependency(dependencyTwo);

            version.RemoveDepedency(dependencyOne);

            Assert.AreEqual(1, version.DependsOn.Count());
            CollectionAssert.Contains(version.DependsOn, dependencyTwo);
        }
        #endregion

        #region AddPackage
        [Test]
        public void AddPackage_PackageForTheVersion_ShouldSucceed()
        {
            var package = Infrastructure.StubsBuilder.BuildPackageStub(version: version);

            version.AddPackage(package);

            Assert.AreEqual(1, version.Packages.Count());
            CollectionAssert.Contains(version.Packages, package);
        }

        [Test]
        public void AddPackage_AddTwice_OnlyOneShouldBeAdded()
        {
            var package = Infrastructure.StubsBuilder.BuildPackageStub(version: version);

            version.AddPackage(package);
            version.AddPackage(package);

            Assert.AreEqual(1, version.Packages.Count());
            CollectionAssert.Contains(version.Packages, package);
        }

        [Test, ExpectedException(typeof(ParentAlreadyAssignedException))]
        public void AddPackage_PackageForAnotherVersion_ShouldThrowException()
        {
            var anotherVersion = StubsBuilder.BuildVersionStub();
            var package = Infrastructure.StubsBuilder.BuildPackageStub(version: anotherVersion);

            version.AddPackage(package);
        }
        #endregion

        #region SetActivePackage
        [Test]
        public void SetActivePackage_PackageAlreadyAdded_ShouldSucceed()
        {
            var package = Infrastructure.StubsBuilder.BuildPackageStub(version: version);
            version.AddPackage(package);

            version.SetActivePackage(package);

            Assert.AreEqual(package, version.ActivePackage);
        }

        [Test, ExpectedException(typeof(InvalidActionException))]
        public void SetActivePackage_PackageNotAdded_ShouldThrowException()
        {
            var package = Infrastructure.StubsBuilder.BuildPackageStub();

            version.SetActivePackage(package);
        }

        [Test, ExpectedException(typeof(InvalidActionException))]
        public void SetActivePackage_PackageFromAnotherVersion_ShouldThrowException()
        {
            var anotherVersion = CreateVersion();
            var package = Infrastructure.StubsBuilder.BuildPackageStub(version: anotherVersion);
            anotherVersion.AddPackage(package);

            version.SetActivePackage(package);
        }
        #endregion

        #region CreateCopy
        [Test]
        public void CreateCopy_ShouldCopyParameterValuesAndDependencies()
        {
            var component = StubsBuilder.BuildComponentStub(false);
            component.Stub(x => x.CreateVersion("", "")).IgnoreArguments().Return(new Version(component, "", ""));
            var dependency1 = StubsBuilder.BuildComponentStub(true);
            var dependency2 = StubsBuilder.BuildComponentStub(true);
            var environment = Operations.StubsBuilder.BuildEnvironmentStub();

            var deploymentTemplate = Infrastructure.StubsBuilder.BuildDeploymentTemplateStub();
            var parameter1 = Infrastructure.StubsBuilder.BuildParameterStub(name: "Parameter1", template: deploymentTemplate);
            var parameter2 = Infrastructure.StubsBuilder.BuildParameterStub(name: "Parameter2", template: deploymentTemplate);
            var parameter3 = Infrastructure.StubsBuilder.BuildParameterStub(name: "Parameter3", template: deploymentTemplate);
            deploymentTemplate.Stub(x => x.Parameters).Return(new IParameter[] { parameter1, parameter2, parameter3 });
            version = CreateVersion(component);
            version.DeploymentDefinition.DeploymentTemplate = deploymentTemplate;
            version.DeploymentDefinition.GetDefaultParameterValue(parameter1).StringValue = "Parameter1Value";
            version.DeploymentDefinition.GetDefaultParameterValue(parameter2).StringValue = "Parameter2Value";
            version.DeploymentDefinition.GetDefaultParameterValue(parameter3).StringValue = "Parameter3Value";
            version.DeploymentDefinition.AddEnvironmentParameterValue(parameter1, environment).StringValue = "Parameter1EnvironmentValue";
            version.DeploymentDefinition.AddEnvironmentParameterValue(parameter3, environment).StringValue = "Parameter3EnvironmentValue";
            version.AddDependency(dependency1);
            version.AddDependency(dependency2);

            var copy = version.CreateCopy();

            Assert.IsInstanceOf<Version>(copy);
            Assert.AreEqual(string.Empty, copy.Name);
            Assert.AreEqual(string.Empty, copy.Description);
            Assert.AreEqual(version.DeploymentDefinition.DeploymentTemplate, copy.DeploymentDefinition.DeploymentTemplate);
            CollectionAssert.AreEquivalent(version.DeploymentDefinition.DefaultParameterValues.Select(x => new { x.Parameter, x.StringValue }).ToArray(), copy.DeploymentDefinition.DefaultParameterValues.Select(x => new { x.Parameter, x.StringValue }).ToArray());
            CollectionAssert.AreEquivalent(version.DeploymentDefinition.EnvironmentParameterValues.Select(x => new { x.Parameter, x.Environment, x.StringValue }).ToArray(), copy.DeploymentDefinition.EnvironmentParameterValues.Select(x => new { x.Parameter, x.Environment, x.StringValue }).ToArray());
            CollectionAssert.AreEquivalent(version.DependsOn, copy.DependsOn);
            Assert.IsNull(copy.ActivePackage);
            CollectionAssert.IsEmpty(copy.Packages);
        }
        #endregion
    }
}
